﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ServiceConsumer.AsyncService;

namespace ServiceConsumer
{
    // NOTE: MVVM pattern was not used so that it don't obscure the whole idea.
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        #region Pending

        private IDisposable Pending()
        {
            return new ActionDisposable(StartPending, StopPending);
        }

        private void StartPending()
        {
            Result.Text = "pending";
            CustomerId = null;
            AddressId = null;
            Exception = null;
            PendingLayer.Visibility = Visibility.Visible;
        }

        private void StopPending()
        {
            PendingLayer.Visibility = Visibility.Collapsed;
        }

        #endregion

        #region Update UI

        // NOTE: MVVM pattern was not used so that it don't obscure the whole idea.

        private int? _customerId;
        private int? _addressId;
        private Exception _exception;

        public int? CustomerId
        {
            get { return _customerId; }
            set
            {
                _customerId = value;
                UpdateResult();
            }
        }

        public int? AddressId
        {
            get { return _addressId; }
            set
            {
                _addressId = value;
                UpdateResult();
            }
        }

        public Exception Exception
        {
            get { return _exception; }
            set
            {
                _exception = value;
                UpdateResult();
            }
        }

        private void UpdateResult()
        {
            if (CustomerId == null && AddressId == null)
                Result.Text = "";
            else if (AddressId == null)
                Result.Text = "Customer added with id: " + CustomerId + "\n";
            else
                Result.Text = "Customer added with id: " + CustomerId + "\n" +
                              "Address added with id: " + AddressId + "\n";

            if (Exception != null)
                Result.Text += Exception.Message;
        }

        #endregion

        private void OnAddCustomerSync(object sender, RoutedEventArgs e)
        {
            StartPending();

            var sync = new Service.SampleServiceClient();
            CustomerId = sync.AddCustomer(CustomerName.Text);

            StopPending();
        }


        private void OnAddCustomerEvent(object sender, RoutedEventArgs e)
        {
            StartPending();

            // We do not need to unsubscribe from completed event because we don't cache service client.
            // If we do the apm could be passed using closure or async userState, 
            // delegate should be assigned to a varaible and passed to itself
            // The first line in delegat would unsubscribe itself from the event.
            var client = new Service.SampleServiceClient();
            client.AddCustomerCompleted += (o, args) =>
                                               {
                                                   CustomerId = args.Result;
                                                   StopPending();
                                               };
            client.AddCustomerAsync(CustomerName.Text);
        }

        private void OnAddCustomerAPM(object sender, RoutedEventArgs e)
        {
            StartPending();

            var apm = new Service.SampleServiceClient();

            apm.BeginAddCustomer(CustomerName.Text,
                ar => Dispatcher.Invoke(() =>
                    {
                        CustomerId = apm.EndAddCustomer(ar);
                        StopPending();
                    }), null);
        }


        private void OnAddCustomerEventWithExceptionHandling(object sender, RoutedEventArgs e)
        {
            StartPending();

            var client = new Service.SampleServiceClient();
            client.AddCustomerCompleted += (o, args) =>
                                               {
                                                   if (args.Error != null)
                                                       Exception = args.Error;
                                                   else
                                                       CustomerId = args.Result;

                                                   StopPending();
                                               };
            client.AddCustomerAsync(CustomerName.Text);
        }

        private void CallAddCustomer(string name, Action<int> continuation,
            Action<Exception> onException = null, Action onFinally = null)
        {
            var client = new Service.SampleServiceClient();
            client.AddCustomerCompleted += (o, args) =>
                                               {
                                                   if (args.Error != null)
                                                   {
                                                       if (onException != null)
                                                           onException(args.Error);
                                                   }
                                                   else
                                                       continuation(args.Result);

                                                   if (onFinally != null)
                                                       onFinally();
                                               };
            client.AddCustomerAsync(name);
        }

        private void CallAddAddress(int customerId, string address, Action<int> continuation,
                                    Action<Exception> onException = null, Action onFinally= null)
        {
            var client = new Service.SampleServiceClient();
            client.AddAddressCompleted += (o, args) =>
                                              {
                                                  if (args.Error != null)
                                                       onException(args.Error);
                                                  else
                                                      continuation(args.Result);

                                                  if (onFinally != null)
                                                    onFinally();
                                              };
            client.AddAddressAsync(customerId, address);
        }

        private void AddCustomerActionWithExceptionHandling(object sender, RoutedEventArgs e)
        {
            StartPending();

            CallAddCustomer(CustomerName.Text, id => { CustomerId = id; },
                            ex => { Exception = ex; }, () => StopPending());
        }

        private void AddCustomerAndAddressAction(object sender, RoutedEventArgs e)
        {
            StartPending();
            CallAddCustomer(CustomerName.Text,
                id =>
                {
                    CustomerId = id;
                    CallAddAddress(id, Address.Text,
                        addressId => { AddressId = addressId; }, 
                        ex => { Exception = ex; }, 
                        () => StopPending());
                },
                ex =>
                {
                    Exception = ex;
                    StopPending();
                });
        }

        private async void AddCustomerAsync(object sender, RoutedEventArgs e)
        {
            StartPending();

            var asyncSerice = new AsyncService.SampleServiceClient();
            CustomerId = await asyncSerice.AddCustomerAsync(CustomerName.Text);

            StopPending();
        }

        private async void AddCustomerAndAddressAsyncWithExceptionHandling(object sender, RoutedEventArgs e)
        {
            StartPending();

            try
            {
                var asyncSerice = new SampleServiceClient();
                CustomerId = await asyncSerice.AddCustomerAsync(CustomerName.Text);
                AddressId = await asyncSerice.AddAddressAsync(CustomerId.Value, Address.Text);
            }
            catch (FaultException exception)
            {
                Exception = exception;
            }
            finally
            {
                StopPending();
            }
        }

       

        private async void AddCustomerAndAddressAsyncWithExceptionHandlingAndDisposable(object sender, RoutedEventArgs e)
        {
            var a = new MyAwaitable();
            await a;
            using (Pending())
            {
                try
                {
                    var asyncSerice = new SampleServiceClient();
                    CustomerId = await asyncSerice.AddCustomerAsync(CustomerName.Text);
                    AddressId = await asyncSerice.AddAddressAsync(CustomerId.Value, Address.Text);
                }
                catch (FaultException exception)
                {
                    Exception = exception;
                }
            }
        }

        private void OnReset(object sender, RoutedEventArgs e)
        {
            CustomerName.Text = "Big Bird";
            Address.Text = "Sesame Street";
        }

        private void OnKill(object sender, RoutedEventArgs e)
        {
            Address.Text = "kill";
        }


        public async Task CallWebSerivce()
        {
            // Dummy call
            await Task.Delay(1000);
        }

        private async void OnDoWork(object sender, RoutedEventArgs e)
        {
            await CallWebSerivce().ConfigureAwait(false);

            // Long running local calculation
            Thread.Sleep(2000);
            // Save to disk, no UI access
        }
    }

    public class Samples
    {
        public async Task AsyncMethod()
        {
            await Task.Delay(1000);
        }

        public Task TaskMethod()
        {
            return Task.Delay(1000);
        }

        public async void Wait()
        {
            await AsyncMethod();
            await TaskMethod();
        }

        public async Task CallWebSerivce()
        {
            // Dummy call
            await Task.Delay(1000);
        }

        public async Task<int> GetOrderTotalPrice(int orderId)
        {
            // Dummy call
            await Task.Delay(1000);

            return 10;
        }

        public async Task<string> GetName()
        {
            await CallWebSerivce();

            return "Big Bird";
        }

        public async void GetNameUse()
        {
            var name = await GetName();
        }

        public async Task DoLongWork()
        {
            await CallWebSerivce();
        }

        public async void DoLongWork2()
        {
            await CallWebSerivce();
        }

        public async Task<int> GetGrandTotalPrice()
        {
            Task<int> t = GetOrderTotalPrice(1);
            Task<int> t2 = GetOrderTotalPrice(2);

            await Task.WhenAll(t, t2);
            return t.Result + t2.Result;
        }
    }

    public class MyAwaitable
    {
        public struct MyAwaiter : INotifyCompletion
        {
            public void OnCompleted(Action continuation)
            {
                throw new NotImplementedException();
            }

            public bool IsCompleted { get; private set; }

            public int GetResult()
            {
                return 5;
            }

        }

        public MyAwaiter GetAwaiter()
        {
            return new MyAwaiter();
        }
    }
}